/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 fireball.h

	$Header: /heroes4/fireball.h $

	$NoKeywords: $

 ************************************************************************/
// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "fireball.h"

#include "abstract_combat_ai_spell_action.h"
#include "adaptor_handler.h"
#include "battlefield.h"
#include "bound_handler.h"
#include "can_cast.h"
#include "combat_ai.h"
#include "combat_creature.h"
#include "combat_footprint.h"
#include "combat_spell_registration.h"
#include "counted_animation.h"
#include "counted_idle_processor.h"
#include "direction.h"
#include "external_string.h"
#include "game_time.h"
#include "magic_mirror.h"
#include "missile_type.h"
#include "move_missile.h"
#include "options.h"
#include "random.h"
#include "replace_keywords.h"
#include "simple_dialog.h"
#include "spell_properties.h"

// ----------------------------------------------------------
// fireball animation
// ----------------------------------------------------------
namespace
{
	class t_cloud_animation : public t_counted_idle_processor, public t_counted_animation
	{
	public:
		t_cloud_animation( t_battlefield& battlefield, t_combat_actor& fireball );

		virtual void on_idle();
	protected:
		void end_animation();

		t_combat_actor& m_cloud;
	};
};

static t_combat_spell_registration<t_fireball>
       k_fireball_register( k_spell_fireball );
static t_combat_spell_registration<t_fireball>
       k_molotov_register( k_spell_kreegan_fire );

// ----------------------------------------------------------
// fireball animation
// ----------------------------------------------------------
t_cloud_animation::t_cloud_animation( t_battlefield& battlefield, t_combat_actor& fireball )
                    : t_counted_idle_processor( 50 ), t_counted_animation( battlefield ),
					  m_cloud( fireball )
{
	set_delay( 100000 / (m_cloud.get_frames_per_second() * get_combat_animation_speed()));
	m_battlefield.preload( fireball, k_combat_actor_action_walk, k_direction_northeast );
	if (fireball.has_action( k_combat_actor_action_walk, k_direction_northeast ))
		m_battlefield.preload( fireball, k_combat_actor_action_postwalk, k_direction_northeast );
}

// ----------------------------------------------------------
// fireball animation
// ----------------------------------------------------------
void t_cloud_animation::end_animation()
{
	animation_ended();
	suspend_idle_processing();
	m_battlefield.remove_object( &m_cloud );
	m_battlefield.update_state();
}

// ----------------------------------------------------------
// fireball animation
// ----------------------------------------------------------
void t_cloud_animation::on_idle()
{
	int frame = m_cloud.get_current_frame_num();
	int frame_count = m_cloud.get_frame_count();
	t_uint32 next_time = get_next_time() - get_delay();
	t_uint32 current_time = get_time();

	while (elapsed_time( current_time, next_time ) >= 0)
	{
		next_time += get_delay();
		frame++;
		if (frame < frame_count)
		{
			m_battlefield.set_current_frame_num( m_cloud, frame );
			if (frame == frame_count - 1)
				break;
		}
		else
		{
			t_combat_actor_action_id action = m_cloud.get_current_action();
			t_idle_ptr               ref = this;

			switch (action)
			{
				case k_combat_actor_action_prewalk:
					m_battlefield.set_current_action( m_cloud, k_combat_actor_action_walk );
					break;

				case k_combat_actor_action_walk:
					if (!m_cloud.has_action( k_combat_actor_action_postwalk,
						                       k_direction_northeast ))
					{
						end_animation();
						return;
					}
					m_battlefield.set_current_action( m_cloud, k_combat_actor_action_postwalk );
					break;

				case k_combat_actor_action_postwalk:
					end_animation();
					return;
			}
			set_next_time( next_time );
			set_delay( 100000 / (m_cloud.get_frames_per_second() * get_combat_animation_speed()));
		}
	}
	set_next_time( next_time );
}

// ----------------------------------------------------------
// Extra data for the impact function; shoved into structure
// because we don't have 4 parameter function handlers...
// ----------------------------------------------------------
struct t_area_spell::t_impact_data
{
	int power;
	bool is_mirror;
};

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
t_area_spell::t_area_spell( t_battlefield& battlefield, t_spell spell )
          : t_combat_spell( battlefield, spell )
{
}

void t_area_spell::ai_cast_spell()
{
	m_battlefield.begin_spell( m_target_square );
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
bool t_area_spell::begin_casting()
{
	std::string help_text;

	if (!check_song_of_peace( *m_caster, m_spell, &help_text ))
	{
		ok_dialog( help_text, true );
		return false;
	}
	return true;
}

// --------------------------------------------------------------------
// targetting information
// --------------------------------------------------------------------
struct t_ai_area_target
{
	t_combat_creature_list	targets;
	double					value;
	t_map_point_2d			target_cell;
};

class t_ai_area_target_list : public std::list<t_ai_area_target>
{
};

// check if left contains all targets in right's list which belong to side.
// assumes that creatures will appear in the same order in both lists.
static bool contains( t_ai_area_target const& left, t_ai_area_target const& right, bool side )
{
	t_combat_creature_list::const_iterator left_index = left.targets.begin();
	t_combat_creature_list::const_iterator left_end = left.targets.end();
	t_combat_creature_list::const_iterator	right_index = right.targets.begin();
	t_combat_creature_list::const_iterator right_end = right.targets.end();

	// find first creature which belongs to "side"
	while (right_index != right_end && (*right_index)->belongs_to_defender() != side)
		++right_index;
	// for each creature that belongs to side...
	while (right_index != right_end)
	{
		// is it anywhere on left's list?
		while (left_index != left_end && *left_index != *right_index)
			++left_index;
		// if not, return false.
		if (left_index == left_end)
			return false;
		// move on to next entry in left's list.
		++left_index;
		// move on to next creature on right's list which belongs to "side".
		do
		{
			++right_index;
		} while (right_index != right_end && (*right_index)->belongs_to_defender() != side);
	}
	return true;
}

static bool is_better( t_ai_area_target const& left, t_ai_area_target const& right, bool side )
{
	// if there are any enemies which are on right's list, but not left's, it's not better.
	if (!contains( left, right, !side ))
		return false;
	// if left contains enemies that are not on right's list, it's better.
	if (!contains( right, left, !side ))
		return true;
	// enemy lists are the same.  Who has fewer friends on the list?
	// if left has only friends that appear on right's list, left is better.
	return contains( right, left, side );
}

// insert an item into a list of targets, but eliminate any duplicate cases, or 
// cases where one target list contains all the targets of the other.
static void insert_target( t_ai_area_target_list& list, t_ai_area_target const& new_target, 
						   bool side )
{
	t_ai_area_target_list::iterator	index = list.begin();
	t_ai_area_target_list::iterator last;

	while (index != list.end())
	{
		if (is_better( *index, new_target, side ))
			return;
		if (is_better( new_target, *index, side ))
		{
			last = index;
			++index;
			list.erase( last );
		}
		else
			++index;
	}
	list.push_back( new_target );
}

// consider placing a given target cell on the list of targets.
void t_area_spell::evaluate_target( t_map_point_2d const& target_cell, 
									t_ai_area_target_list& list ) const
{
	if (!m_battlefield.can_see_cell( *m_caster, target_cell ))
		return;

	t_ai_area_target target;

	target.targets = get_targets( m_caster, target_cell );
	if (target.targets.empty())
		return;

	t_combat_creature_list::iterator	index = target.targets.begin();
	t_combat_creature_list::iterator	end = target.targets.end();

	target.value = 0.0;
	for (; index != target.targets.end(); index++)
	{
		target.value += ai_weight( **index );
	}
	if (target.value <= 0.0)
		return;

	target.target_cell = target_cell;
	insert_target( list, target, m_caster->get_controller() );
}

static t_map_rect_2d get_target_extent( t_combat_creature const&	target,
										int						radius )
{
	t_map_rect_2d	rect;
	int				offset;
	t_map_point_2d	center;

	offset = radius + target.get_half_footprint_size();	
	center = target.get_footprint_center();
	rect.top_left = center;
	rect.bottom_right  = center;
	rect.top_left.column -= offset;
	rect.top_left.row -= offset;
	rect.bottom_right.column += offset;
	rect.bottom_right.row += offset;
	rect.top_left.column >>= k_battlefield_subcell_shift;
	rect.top_left.row >>= k_battlefield_subcell_shift;
	rect.bottom_right.column = (rect.bottom_right.column + k_battlefield_subcells_per_cell - 1) 
										>> k_battlefield_subcell_shift;
	rect.bottom_right.row = (rect.bottom_right.row + k_battlefield_subcells_per_cell - 1 )
									>> k_battlefield_subcell_shift;
	return rect;
}

void t_area_spell::evaluate_overlap( t_combat_creature const&	primary,
									 t_combat_creature const&	secondary,
									 t_ai_area_target_list&		list ) const
{
	t_map_rect_2d	rect;
	t_map_rect_2d	secondary_rect;
	int				radius = get_radius();

	rect = get_target_extent( primary, radius );
	secondary_rect = get_target_extent( secondary, radius );
	rect = intersection( rect, secondary_rect );
	if (rect.height() <= 0 || rect.width() <= 0)
		return;

	t_battlefield&	battlefield = m_battlefield;
	t_map_point_2d	point;

	for (point.row = rect.top(); point.row < rect.bottom(); ++point.row)
	{
		for (point.column = rect.left(); point.column < rect.right(); ++point.column)
		{
			if (!battlefield.is_valid( point ))
				continue;
			evaluate_target( point, list );
		}
	}
}


void t_area_spell::evaluate_targets( t_ai_area_target_list&   list ) const
{
	t_battlefield&						battlefield = m_battlefield;
	t_combat_creature_list::iterator	index = battlefield.creatures_begin();
	t_combat_creature_list::iterator	second_index;
	t_combat_creature_list::iterator	end = battlefield.creatures_end();
	t_combat_creature const*			target;
	t_combat_creature const*			second_target;

	// first, try attacking the creature directly.
	for (; index != end; ++index)
	{
		target = *index;
		if (target->get_number() == 0)
			continue;
		if (target->belongs_to_defender() == m_caster->get_controller())
			continue;
		if (battlefield.can_see( *m_caster, *target ))
		{
			t_map_point_2d target_cell;

			target_cell = target->get_footprint_center() >> k_battlefield_subcell_shift;
			target_cell = target->get_impact_point( get_cell(m_caster->get_footprint_center()),
												    target_cell );
			evaluate_target( target_cell, list );
		}
		// next, consider overlaps with nearby creatures.
		second_index = index;
		for (++second_index; second_index != end; ++second_index)
		{
			second_target = *second_index;
			if (second_target->get_number() == 0)
				continue;
			if (second_target->belongs_to_defender() == m_caster->get_controller())
				continue;
			evaluate_overlap( *target, *second_target, list );
		}
	}
}

// --------------------------------------------------------------------
// Generate the list of casting actions the given caster could take
// for this spell
// --------------------------------------------------------------------
t_combat_ai_action_list t_area_spell::generate_combat_ai_action_list(t_combat_ai &owner)
{
	t_combat_ai_action_list			action_list;
	t_ai_area_target_list			target_list;
	t_ai_area_target_list::iterator index;
	t_ai_area_target*				best_target = 0;

	if (m_caster->is_active( k_spell_song_of_peace ))
		return action_list;
	if (!spellcasting_is_worthwhile( m_battlefield, m_caster->get_controller() ))
		return action_list;
	evaluate_targets( target_list );
	if (target_list.empty())
		return action_list;

	for (index = target_list.begin(); index != target_list.end(); ++index)
	{
		if (best_target == 0 || index->value > best_target->value)
		{
			best_target = &(*index);
			m_target_square = best_target->target_cell;
		}
	}
	action_list.push_back( new t_combat_ai_spell_action( m_battlefield, this, best_target->value ) );
	return action_list;
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
t_combat_creature_list t_area_spell::get_targets( t_combat_creature_ptr caster,
												  t_map_point_2d const& point ) const
{
	t_combat_creature_list			 targets;
	t_combat_creature_list::iterator index = m_battlefield.creatures_begin();
	t_combat_creature_list::iterator end = m_battlefield.creatures_end();
	t_combat_creature*				 creature;
	int								 radius;
	t_map_point_2d					 center = get_cell_center( point );

	radius = get_radius();
	// find who is actually in the area
	for (; index != end; index++)
	{
		creature = index->get();

		if (creature->get_number() <= 0)
			continue;

		if (creature->get_edge_subcell_distance( center ) >= radius)
			continue;

		if (!can_affect( *caster, m_spell, *creature ))
			continue;

		if (m_battlefield.is_in_castle( creature->get_cell_position() )
			&& !m_battlefield.is_in_castle( point ))
			continue;

		targets.push_back( creature );
	}
	return targets;
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
bool t_area_spell::can_cast( t_map_point_2d const& center ) const
{
	t_combat_creature_list targets;

	if (!m_battlefield.can_see_cell( *m_caster, center ))
		return false;

	targets = get_targets( m_caster, center );

	t_combat_creature_list::iterator index = targets.begin();
	t_combat_creature_list::iterator end = targets.end();

	for (; index != targets.end(); index++)
	{
		if (!m_caster->is_friendly( **index ))
			return true;
	}
	return false;
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
bool t_area_spell::can_cast( t_screen_point const& point ) const
{
	t_map_point_2d         center;

	if (!get_map_point( point, center ))
		return false;
	return can_cast( center );
}


// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
void t_area_spell::execute( t_combat_creature& caster )
{
	t_combat_creature_list			 targets;
	t_combat_creature_list::iterator index;
	t_combat_creature_list::iterator end;
	int								 power;

	targets = get_targets( m_caster, m_target_square );

	power = m_caster->get_spell_power( m_spell );
	index = targets.begin();
	end	  = targets.end();
	pay_cost();
	for (; index != targets.end(); index++)
	{
		if (index->get()->has_magic_mirror())
			m_battlefield.add_action( new t_mirror_spell_action( *index, this, power ) );
	}
	if (!m_is_artifact)
		m_caster->set_default_spell( m_spell );
	cast( m_caster, m_target_square, m_caster->get_spell_power( m_spell ), false );
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
void t_area_spell::launch( t_missile_type missile_type, t_combat_creature_ptr caster, 
						   t_map_point_2d target_square, int power, bool is_mirror )
{
	t_map_point_3d					 target_center;
	t_missile_handler				 handler;
	t_map_point_3d					 source_position;
	t_map_point_2d					 source_cell;
	t_combat_creature*				 creature;
	t_combat_creature_list			 targets;
	t_combat_creature_list::iterator index;
	t_combat_creature_list::iterator end;
	t_ranged_result					 obscured;
	t_combat_action_message			 message( *caster, get_action_text( is_mirror ) );

	source_position = caster->get_missile_offset();
	source_cell = source_position >> k_battlefield_subcell_shift;
	creature = m_battlefield.get_ranged_target( *caster, 
		                                        get_cell_center(target_square),
												obscured );
	if (creature != 0)
		target_square = creature->get_impact_point( source_cell, target_square );
	targets = get_targets( caster, target_square );
	index = targets.begin();
	end = targets.end();
	for (; index != targets.end(); index++)
		m_battlefield.preload_flinch( **index );
	target_center.row = target_square.row;
	target_center.column = target_square.column;
	target_center <<= k_battlefield_subcell_shift;
	target_center += k_battlefield_half_cell;
	target_center.height = m_battlefield.compute_terrain_height( target_center );

	t_impact_data impact_data;
	impact_data.power = power;
	impact_data.is_mirror = is_mirror;
	handler = add_3rd_argument( bound_handler( *this, &t_area_spell::impact ), impact_data );

	m_battlefield.launch_missile( missile_type, *caster, source_position, 
								  target_center, handler, message );

}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
void t_area_spell::execute_mirror_spell( t_combat_creature_ptr creature, int power )
{
	t_map_point_2d    target_square;

	target_square = get_cell( m_caster->get_footprint_center() );
	m_battlefield.set_spell( this );
	cast( creature, target_square, power, true );
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
double	t_area_spell::get_square_value( t_map_point_2d const& target ) const
{
	t_combat_creature_list           targets;
	t_combat_creature_list::iterator index;
	double							 result = 0;

	targets = get_targets( m_caster, target );

	for (index = targets.begin(); index != targets.end(); ++index)
		result += ai_weight( **index );
	return result;
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
bool t_area_spell::get_map_point( t_screen_point const& point, t_map_point_2d& center ) const
{
	if (!m_battlefield.cell_hit_test( point, center ))
		return false;

	t_combat_creature*	target;

	target = m_battlefield.creature_hit_test( point );
	return m_battlefield.get_area_range_target( target, center, get_radius());
}


// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
extern t_external_string const k_text_cannot_see_cell;

t_mouse_window* t_area_spell::mouse_move( t_screen_point const& point,
									    std::string& help_text )
{
	t_combat_creature_list targets;
	t_map_point_2d         center;
	t_mouse_window*        result;
	t_combat_creature*     creature;

	result = m_battlefield.get_blocked_cursor();
	if (!get_map_point( point, center ))
	{
		help_text = replace_spell_keywords( k_text_cannot_see_cell, *m_caster, m_spell, 0 );
		return result;
	}

	creature = m_battlefield.get_creature( center );
	if (!check_line_of_sight( *m_caster, m_spell, center, &help_text ))
	{
		m_battlefield.select_target( creature );
		return result;
	}

	targets = get_targets( m_caster, center );

	if (contains_enemies( *m_caster, m_spell, targets, &help_text ))
	{
		m_battlefield.select_targets( targets );
		return m_battlefield.get_spell_cursor();
	}
	m_battlefield.select_target( creature );
	if (creature != 0)
		::can_cast( *m_caster, m_spell, *creature, &help_text );
	return result;
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
bool t_area_spell::left_click( t_screen_point const& point )
{
	t_map_point_2d     center;

	if (!get_map_point( point, center ))
		return false;
	if (!can_cast( center ))
		return false;

	m_target_square = center;
	m_battlefield.begin_spell( center );
	return true;
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
void t_area_spell::place_spell( t_combat_actor_model_ptr model, t_map_point_2d target_point, bool is_mirror )
{
	t_combat_actor_ptr cloud;
	int				   half_footprint;
	t_cloud_animation* cloud_animation;
	t_combat_action_message message( *m_caster, get_action_text( is_mirror ) );

	cloud = new t_unsaved_combat_actor( &m_battlefield, model, k_combat_actor_action_prewalk, 
		                        k_direction_northeast );
	if (!cloud->has_action( k_combat_actor_action_prewalk, k_direction_northeast ))
		cloud->set_current_action( k_combat_actor_action_walk );
	half_footprint = cloud->get_half_footprint_size();
	target_point.row -= half_footprint;
	target_point.column -= half_footprint;
	if (m_battlefield.off_map( *cloud, get_cell( target_point ) ))
		return;
	m_battlefield.place_object( cloud, target_point );
	cloud_animation = new t_cloud_animation(m_battlefield, *cloud );
	cloud_animation->display_action_message( message );
	cloud->set_animation( cloud_animation );
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
void t_area_spell::impact( t_combat_creature_ptr caster, t_map_point_2d target_point,
						   t_impact_data data )
{
	t_combat_creature_list targets;
	t_map_point_2d         target_cell = target_point >> k_battlefield_subcell_shift;

	targets = get_targets( caster, target_cell );

	t_combat_creature_list::iterator index = targets.begin();
	t_combat_creature_list::iterator end = targets.end();

	play_sound();
	for (; index != targets.end(); index++)
	{
		cast_on( caster, *index, data.power, data.is_mirror );
	}
	show_impact( target_point, data.is_mirror );
	m_battlefield.set_spell( 0 );
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
int t_area_spell::get_radius() const
{
	return 4 * k_battlefield_subcells_per_cell;
}


// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
t_fireball::t_fireball( t_battlefield& battlefield, t_spell spell )
          : t_area_spell( battlefield, spell)
{
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
bool t_fireball::cast_on( t_combat_creature_ptr caster, 
						  t_combat_creature_ptr target, 
						  int power,
						  bool is_mirror )
{
	int damage = target->modify_spell_damage( caster, power, m_spell );
	t_combat_action_message message( *caster, get_action_text( is_mirror ) );

	target->add_damage( damage, caster, false, false, message );
	target->check_flinch( message );
	target->apply_damage( m_battlefield, true );
	return true;
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
void t_fireball::cast( t_combat_creature_ptr caster, t_map_point_2d target_square,
					   int power, bool is_mirror )
{
	t_missile_type missile_type;

	if (is_potion())
		missile_type = k_missile_potion;
	else
		missile_type = k_missile_fireball;
	launch( missile_type, caster, target_square, power, is_mirror );
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
double t_fireball::ai_weight( t_combat_creature const& target ) const
{
	if (!can_affect( *m_caster, m_spell, target))
		return 0;

	return get_damage_spell_weight( *m_caster, target, m_spell );
}

double t_fireball::get_cancel_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
void t_fireball::place_fireball( t_map_point_2d target_point, bool is_mirror )
{
	static t_combat_actor_model_cache_set k_fireball_model( "spells.fireball" );

	place_spell( k_fireball_model.get( m_battlefield.get_model_scale() ),
		         target_point, is_mirror );
}


// ----------------------------------------------------------
// handle fireball spell
// ----------------------------------------------------------
void t_fireball::show_impact( t_map_point_2d target_point, bool is_mirror )
{
	place_fireball( target_point, is_mirror );
}


// ----------------------------------------------------------
// handle inferno spell
// ----------------------------------------------------------
namespace
{
	class t_inferno : public t_fireball
	{
	public:
		t_inferno( t_battlefield& battlefield, t_spell spell );
	protected:
		virtual int  get_radius() const;
		virtual void show_impact( t_map_point_2d target_point, bool is_mirror );
	};
};

static t_combat_spell_registration<t_inferno>
       k_inferno_register( k_spell_inferno );


// ----------------------------------------------------------
// handle inferno spell
// ----------------------------------------------------------
t_inferno::t_inferno( t_battlefield& battlefield, t_spell spell ) 
         : t_fireball( battlefield, spell )
{
}

// ----------------------------------------------------------
// handle inferno spell
// ----------------------------------------------------------
int t_inferno::get_radius() const
{
	return 7 * k_battlefield_subcells_per_cell;
}

// ----------------------------------------------------------
// handle inferno spell
// ----------------------------------------------------------
void t_inferno::show_impact( t_map_point_2d center, bool is_mirror )
{
	int				radius = get_radius() - t_fireball::get_radius();
	int				i;
	t_direction		direction;
	t_map_point_2d  point;

	place_fireball( center, is_mirror );
	direction = k_direction_north;
	for (i = 0; i < 4; i++)
	{
		point = center + get_direction_offset( direction ) * radius;
		place_fireball( point, is_mirror );
		direction = clockwise( direction, 2 );
	}
}

namespace
{
// ----------------------------------------------------------
// release fireballs for Armageddon
// ----------------------------------------------------------
	class t_armageddon;

	class t_fireball_releaser : public t_idle_processor, public t_counted_animation
	{
	public:
		t_fireball_releaser( t_battlefield& battlefield );

		void add( t_map_point_2d const& target );
		void set( t_armageddon* spell );
		void start();

		virtual void on_idle();
	protected:
		t_map_point_3d compute_origin( t_map_point_3d const& dest,
			                           t_map_point_3d const& offset ) const;
		t_armageddon*			    m_spell;
		std::vector<t_map_point_2d> m_targets;
	};

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
	class t_armageddon : public t_fireball
	{
	public:
		t_armageddon( t_battlefield& battlefield, t_spell spell );

		virtual void	ai_cast_spell();
		virtual bool    begin_casting();
		virtual void    execute( t_combat_creature& caster );
		virtual t_combat_ai_action_list generate_combat_ai_action_list(t_combat_ai &owner);
		void			impact( t_combat_creature_ptr caster, t_map_point_2d square, bool is_mirror );
	protected:
		t_fireball_releaser	m_releaser;
	};
};


static t_combat_spell_registration<t_armageddon>
       k_armageddon_register( k_spell_armageddon );

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
t_armageddon::t_armageddon( t_battlefield& battlefield,  t_spell spell )
            : t_fireball( battlefield, spell ), m_releaser( battlefield )
{
	m_releaser.set( this );
}

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
extern t_external_string const k_text_no_effect;

void t_armageddon::ai_cast_spell()
{
	m_battlefield.begin_spell( 0 );
}

bool t_armageddon::begin_casting()
{
	t_combat_creature_list::iterator index = m_battlefield.creatures_begin();
	t_combat_creature_list::iterator end   = m_battlefield.creatures_end();
	t_combat_spell_ptr               spell_object;
	std::string                      help_text;

	for (; index != end; index++)
	{
		if (can_affect( *m_caster, m_spell, **index ))
			break;
	}
	if (index == end)
	{
		std::string text;

		text = replace_keywords( k_text_no_effect, "%spell_name", get_spell_name( m_spell ));
		ok_dialog( text, true );
		return false;
	}
	m_battlefield.begin_spell( 0 );
	return true;
}

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
void t_armageddon::execute( t_combat_creature& caster )
{
	// add all the creatures into the target list
	t_combat_creature_list::iterator creature = m_battlefield.creatures_begin();
	t_combat_creature_list::iterator end	  = m_battlefield.creatures_end();
	t_map_point_2d                   point;
	t_combat_action_message          message( caster, get_action_text( false ) );

	m_releaser.display_action_message( message );

	for (; creature != end; creature++)
	{
		if (can_affect( *m_caster, m_spell, **creature ))
		{
			point = get_cell( creature->get()->get_footprint_center() );
			m_releaser.add( point );
		}
	}
	play_sound();
	m_releaser.start();
}

// --------------------------------------------------------------------
// Generate the list of casting actions the given caster could take
// for this spell
// --------------------------------------------------------------------
t_combat_ai_action_list t_armageddon::generate_combat_ai_action_list(t_combat_ai &owner)
{
	t_combat_ai_action_list list;

	// add all the creatures into the target list
	t_combat_creature_list::iterator creature = m_battlefield.creatures_begin();
	t_combat_creature_list::iterator end	  = m_battlefield.creatures_end();
	double							 value = 0.0;

	for (; creature != end; creature++)
	{
		if (can_affect( *m_caster, m_spell, **creature ))
		{
			value += ai_weight( **creature );
		}
	}
	if (value <= 0.0)
		return t_combat_ai_action_list();

	list.push_back( new t_combat_ai_spell_action( m_battlefield, this, value ));
	return list;
}

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
void t_armageddon::impact( t_combat_creature_ptr caster, t_map_point_2d subcell, bool is_mirror )
{
	t_map_point_2d     cell = get_cell( subcell );
	t_combat_creature* target = m_battlefield.get_creature( cell );
	int                power = caster->get_spell_power( m_spell );

	assert( !is_mirror ); // Never mirror Armageddon

	if (target != 0)
		cast_on( caster, target, power, is_mirror );
	place_fireball( subcell, is_mirror );
	m_battlefield.update_state();
}

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
t_fireball_releaser::t_fireball_releaser( t_battlefield& battlefield )
                   : t_idle_processor( 1, 50 ), t_counted_animation( battlefield )
{
}

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
void t_fireball_releaser::add( t_map_point_2d const& target )
{
	m_targets.push_back( target );
}

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
void t_fireball_releaser::set( t_armageddon* spell )
{
	m_spell = spell;
}

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
void t_fireball_releaser::on_idle()
{
	int count = 1;
	int choice;
	t_map_point_2d    target;
	t_missile_handler handler;
	t_map_point_3d    target_subcell;
	t_map_point_3d    origin;
	t_map_point_3d    offset;
	int				  distance = 40 << k_battlefield_subcell_shift;
	double			  angle = k_pi;
	double			  ground_angle = k_pi / 4;

	offset.row = -sin( angle ) * distance;
	offset.column = cos( angle ) * distance;
	offset.height = sin( ground_angle ) * distance;
	handler = add_3rd_argument( bound_handler( *m_spell, &t_armageddon::impact ), false );
	while (count--)
	{
		choice = random( m_targets.size() );
		target = m_targets[choice];
		m_targets.erase( m_targets.begin() + choice );
		target_subcell = m_battlefield.get_cell_center( target );
		origin = compute_origin( target_subcell, offset );
		m_battlefield.launch_missile( k_missile_armageddon, *m_spell->get_caster(),
			                          origin, target_subcell, handler, get_action_message() );
		if (m_targets.empty())
		{
			animation_ended();
			suspend_idle_processing();
			return;
		}
	}
}

// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
t_map_point_3d t_fireball_releaser::compute_origin( t_map_point_3d const& dest,
			                                        t_map_point_3d const& offset ) const
{
	t_map_point_3d sum(0,0,0);
	int			   divisor;
	t_map_point_3d result = dest;
	t_map_point_3d new_point;
	int            i;
	t_map_point_3d delta = offset << k_battlefield_subcell_shift;
	int			   cells;

	divisor = abs( offset.column );
	if (divisor < abs( offset.row ))
		divisor = abs( offset.row );
	cells = divisor >> k_battlefield_subcell_shift;
	for (i = 0; i < cells; i++)
	{
		sum += delta;
		new_point = dest + sum / divisor;
		if (!m_battlefield.is_valid( get_cell( new_point )))
			break;
		result = new_point;
	}
	return result;
}


// ----------------------------------------------------------
// handle Armageddon spell
// ----------------------------------------------------------
void t_fireball_releaser::start()
{
	resume_idle_processing();
}

namespace
{
// ----------------------------------------------------------
// handle Cloud of Confusion
// ----------------------------------------------------------
	class t_cloud_of_confusion : public t_area_spell
	{
	public:
		t_cloud_of_confusion( t_battlefield& battlefield, t_spell spell );

		virtual bool	cast_on( t_combat_creature_ptr caster,
			                     t_combat_creature_ptr target, 
								 int power,
								 bool is_mirror );
		virtual double	get_cancel_weight( t_combat_creature const& target ) const;
		virtual double	ai_weight( t_combat_creature const& target ) const;
	protected:
		virtual void	cast( t_combat_creature_ptr caster, 
							  t_map_point_2d        target_square,
							  int				    power,
							  bool					is_mirror );
		virtual void	show_impact( t_map_point_2d target_point, bool is_mirror );
	};
};

static t_combat_spell_registration<t_cloud_of_confusion>
       k_confusion_register( k_spell_cloud_of_confusion );

// ----------------------------------------------------------
// handle Cloud of Confusion
// ----------------------------------------------------------
t_cloud_of_confusion::t_cloud_of_confusion( t_battlefield& battlefield, t_spell spell )
					: t_area_spell( battlefield, spell )
{
}

// ----------------------------------------------------------
// handle Cloud of Confusion
// ----------------------------------------------------------
bool t_cloud_of_confusion::cast_on( t_combat_creature_ptr caster,
								    t_combat_creature_ptr target, 
									int power,
									bool is_mirror )
{
	t_combat_action_message message( *caster, get_action_text( is_mirror ) );
	return m_battlefield.cast_spell( *caster, *target, k_spell_confusion, message, power );
}

// ----------------------------------------------------------
// handle Cloud of Confusion
// ----------------------------------------------------------
double t_cloud_of_confusion::ai_weight( t_combat_creature const& target ) const
{
	if (target.is_incapacitated() || target.get_controller() == m_caster->get_controller())
		return 0.0;
	if (m_battlefield.get_active_creature_count( m_caster->get_controller() ) < 2)
		return 0.0;
	if (!spellcasting_is_worthwhile( m_battlefield, false ) || !spellcasting_is_worthwhile( m_battlefield, true ))
		return 0.0;
	if (get_approximate_lifespan( m_battlefield, target ) < 2)
		return 0.0;
	return target.get_ai_value_per_action() * m_caster->get_spell_chance( target, m_spell ) * 0.01
		* t_combat_spell::k_duration_confusion;
}

double t_cloud_of_confusion::get_cancel_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// ----------------------------------------------------------
// handle Cloud of Confusion
// ----------------------------------------------------------
void t_cloud_of_confusion::cast( t_combat_creature_ptr caster, 
								 t_map_point_2d        target_square,
								 int				   power,
								 bool				   is_mirror )
{
	t_impact_data data;
	data.power = power;
	data.is_mirror = is_mirror;
	impact( caster, get_cell_center(target_square), data );
}

// ----------------------------------------------------------
// handle Cloud of Confusion
// ----------------------------------------------------------
static t_combat_actor_model_cache_set k_cloud_model( "spells.cloud of confusion" );

void t_cloud_of_confusion::show_impact( t_map_point_2d target_point, bool is_mirror )
{
	place_spell( k_cloud_model.get( m_battlefield.get_model_scale() ),
		         target_point, is_mirror );
}

namespace
{
// ----------------------------------------------------------
// handle Choking Gas
// ----------------------------------------------------------
	class t_choking_gas : public t_fireball
	{
	public:
		t_choking_gas( t_battlefield& battlefield, t_spell spell );

		virtual bool			cast_on( t_combat_creature_ptr caster,
			                             t_combat_creature_ptr target, 
										 int power,
										 bool is_mirror );
		virtual double			ai_weight( t_combat_creature const& target ) const;
	protected:
		virtual void           show_impact( t_map_point_2d target_point, bool is_mirror );
	};
};

static t_combat_spell_registration<t_choking_gas>
       k_gas_register( k_spell_gas );

// ----------------------------------------------------------
// handle Choking Gas
// ----------------------------------------------------------
t_choking_gas::t_choking_gas( t_battlefield& battlefield, t_spell spell )
             : t_fireball( battlefield, spell )
{
}

// ----------------------------------------------------------
// handle Choking Gas
// ----------------------------------------------------------
bool t_choking_gas::cast_on( t_combat_creature_ptr caster,
							 t_combat_creature_ptr target, 
							 int power,
							 bool is_mirror )
{
	if (!m_battlefield.check_resistance( *caster, *target, m_spell ))
		return false;
	t_combat_action_message message( *caster, get_action_text( is_mirror ) );
	m_battlefield.start_animation( *target, m_spell, message );
	target->set_animation( k_combat_actor_action_flinch, message );
	target->set_spell( m_spell, message, power );
	return true;
}

// ----------------------------------------------------------
// handle Choking Gas
// ----------------------------------------------------------
double t_choking_gas::ai_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// ----------------------------------------------------------
// handle Choking Gas
// ----------------------------------------------------------
void t_choking_gas::show_impact( t_map_point_2d target_point, bool is_mirror )
{
	static t_combat_actor_model_cache_set k_gas_model( "spells.choking gas" );

	place_spell( k_gas_model.get( m_battlefield.get_model_scale() ),
		         target_point, is_mirror );
}

namespace
{
// ----------------------------------------------------------
// handle Cloud of Despair
// ----------------------------------------------------------
	class t_cloud_of_despair : public t_choking_gas
	{
	public:
		t_cloud_of_despair( t_battlefield& battlefield, t_spell spell );

		virtual double	ai_weight( t_combat_creature const& target ) const;
		virtual void	show_impact( t_map_point_2d target_point, bool is_mirror );
	};
};

static t_combat_spell_registration<t_cloud_of_despair>
       k_despair_register( k_spell_despair );

// ----------------------------------------------------------
// handle Cloud of Despair
// ----------------------------------------------------------
t_cloud_of_despair::t_cloud_of_despair( t_battlefield& battlefield, t_spell spell )
                  : t_choking_gas( battlefield, spell )
{
}

double t_cloud_of_despair::ai_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

void t_cloud_of_despair::show_impact( t_map_point_2d target_point, bool is_mirror )
{
	place_spell( k_cloud_model.get( m_battlefield.get_model_scale() ),
		         target_point, is_mirror );
}
